package com.demo.interceptor;


import com.demo.service.OpenServerAppService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

/**
 * API 认证拦截器
 * 职责：校验 app_id、签名、防重放、限流
 * @author gy
 */
@Component
@Slf4j
public class ApiAuthInterceptor implements HandlerInterceptor {

    private static final String[] IP_HEADERS = {
            "X-Forwarded-For",
            "Proxy-Client-IP",
            "WL-Proxy-Client-IP",
            "HTTP_CLIENT_IP",
            "HTTP_X_FORWARDED_FOR"
    };

    @Autowired
    private OpenServerAppService openServerAppService;

    @Autowired
    private NonceCache nonceCache;

    @Autowired
    private RedisRateLimiter redisRateLimiter;


    /**
     * 请求随机数失效时间，默认记录 1分钟的 请求
     */
    private static final Integer NONCE_EXPIRE_TIME = 60;

    @Override
    public boolean preHandle(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler) throws Exception {
        // 1. 提取请求头
        String appId = request.getHeader("X-App-Id");
        String nonce = request.getHeader("X-Nonce");
        String timestamp = request.getHeader("X-Timestamp");
        String signature = request.getHeader("X-Signature");

        // 使用servlet路径 排除 context-path 路径的影响
        String path = request.getServletPath();

        if (StringUtils.isBlank(appId) || StringUtils.isBlank(nonce) ||
                StringUtils.isBlank(timestamp) || StringUtils.isBlank(signature)) {
            sendError(response, HttpServletResponse.SC_BAD_REQUEST,  "Missing required headers");
            return false;
        }

        // 2. 验证 app_id 有效性
//        OpenServerApp openServerApp = openServerAppService.getServerAppByAppId(appId);
//        String appName = openServerApp.getAppName();
//        if (!openServerAppService.appIdIsValid(openServerApp)) {
//            sendError(response, 401, "Invalid App ID");
//            return false;
//        }

        // 根据请求方法获取参数（区分 GET/POST）
        String method = request.getMethod().toUpperCase();
        String requestData;
        switch (method) {
            case "GET":
            case "DELETE":
                // GET DELETE 等请求: 从 URL 查询参数中提取
                requestData = HttpHelper.getQueryParameters(request);
                break;
            case "POST":
            case "PUT":
            case "PATCH":
                // POST/PUT 等请求: 从请求体中提取
                requestData = HttpHelper.getBodyString(request);
                break;
            default:
                sendError(response, 405, "Method not allowed");
                return false;
        }

        // 3. 防重放攻击
        // 3.1 nonce验证
//        if (nonceCache.isNonceUsed(nonce)) {
//            sendError(response, 427, "Duplicate request detected:nonce已使用");
//            return false;
//        }
        // 3.2 时间戳验证
        LocalDateTime pastTime = this.convertTimestampToDatetime(Long.parseLong(timestamp));
        LocalDateTime curTime = LocalDateTime.now();
        if (Duration.between(pastTime, curTime).getSeconds() > NONCE_EXPIRE_TIME){
            sendError(response, 428, "Duplicate request detected:nonce已失效");
            return false;
        }

        // 4. 验证签名
//        String appSecret = openServerApp.getAppSecret();
//        String expectedSignature = HmacSigner.generateSignature(
//                appSecret,
//                method,
//                path,
//                timestamp,
//                nonce,
//                requestData
//        );
//        // 服务端生成的预期签名与第三方传过来的进行校对
//        if (!expectedSignature.equals(signature)) {
//            sendError(response, 403, "Invalid signature");
//            return false;
//        }

        // 5. 限流检查
//        int concurrencyNum = openServerApp.getConcurrencyNum() == null ? 10 : openServerApp.getConcurrencyNum();
        String rateLimiterKey = getRateLimiterKey(appId,path);
        // 每秒concurrencyNum次
        // 推荐默认值：capacity = 100, refillRate = 10（适用于通用场景）。
        // 调整依据：根据系统资源、业务场景和监控数据动态优化。
        // 高级策略：支持自适应限流和租户分级，提升灵活性和公平性
//        if (!redisRateLimiter.tryAcquire(rateLimiterKey, concurrencyNum * 10, concurrencyNum)) {
//            sendError(response, 429, "Rate limit exceeded");
//            return false;
//        }

        // 6. 记录Nonce
//        nonceCache.recordNonce(nonce, NONCE_EXPIRE_TIME);

        // 7. 日志记录额外信息
        openApiLogInterceptorInfo(request, response, nonce, appId);

        return true;
    }

    /**
     * 按照接口路径进行限流
     * @param path
     * @param appId
     * @return
     */
    private static String getRateLimiterKey(String appId,String path) {
        StringBuilder pathBuilder = new StringBuilder();
        pathBuilder.append(appId);
        if (StringUtils.isNotBlank(path)) {
            path = path.replaceFirst("^/", "");
            String[] pathArr = path.split("/");
            for (String pathStr : pathArr) {
                pathBuilder.append(":").append(pathStr);
            }
        }
        return pathBuilder.toString();
    }

    /**
     * open api log 记录相关信息
     * @param request
     * @param response
     * @param nonce 随机数 当作 请求id requestId
     * @param appId 业务id
     */
    private void openApiLogInterceptorInfo(HttpServletRequest request, HttpServletResponse response,
                                           String nonce, String appId) {

        // 获取客户端IP
        String clientIp = resolveClientIp(request);
        request.setAttribute("clientIp", clientIp);

        // 获取业务标识
        request.setAttribute("appId", appId);

        // 日志MDC设置
        MDC.put("requestId", nonce);
        MDC.put("clientIp", clientIp);
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler, Exception ex) {
        // 清理MDC
        MDC.remove("requestId");
        MDC.remove("clientIp");
    }


    /**
     * 返回错误响应
     */
    private void sendError(HttpServletResponse response, int code, String message) throws IOException {
        if (response.isCommitted()) {
            // 如果响应已经提交，则只记录错误但不再尝试写入
            return;
        }
        response.setStatus(code);
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        String errorJson = "{\"error\":\"" + message + "\"}";

        try {
            // 首先尝试使用 Writer
            response.getWriter().write(errorJson);
            response.getWriter().flush();
        } catch (IllegalStateException e) {
            // 如果已经使用了 OutputStream，则尝试使用 OutputStream
            try {
                response.getOutputStream().write(errorJson.getBytes(StandardCharsets.UTF_8));
                response.getOutputStream().flush();
            } catch (Exception ex) {
                // 两种方式都失败，只能放弃写入响应
            }
        }
    }


    /**
     * 解析客户端真实IP
     */
    private String resolveClientIp(HttpServletRequest request) {
        // 检查代理头
        for (String header : IP_HEADERS) {
            String ip = request.getHeader(header);
            if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
                return ip.split(",")[0].trim();
            }
        }
        return request.getRemoteAddr();
    }


    /**
     * String转LocalDateTime
     *
     * @param timestamp
     * @return
     */
    public static java.time.LocalDateTime convertTimestampToDatetime(long timestamp) {
        // 将时间戳转换为Instant对象
        Instant instant = Instant.ofEpochMilli(timestamp);

        // 将Instant对象转换为在默认时区的LocalDateTime对象
        java.time.LocalDateTime dateTime = java.time.LocalDateTime.ofInstant(instant, ZoneId.systemDefault());

        return dateTime;
    }

}